package com.qust.endecryptionmanager.service.imp;

import com.alibaba.fastjson2.JSONObject;
import com.qust.agreement.Message;
import com.qust.agreement.message.AlgorithmicResourceCatchUpMessage;
import com.qust.agreement.message.TransmissionAlgorithmMessage;
import com.qust.agreement.result.Result;
import com.qust.encryptionAndDecryption.IEnDecryption;
import com.qust.encryptionAndDecryption.KeyPair;
import com.qust.encryptionAndDecryption.enDecryption.AbstractEnDecryptionWithKeyPair;
import com.qust.endecryptionmanager.mapper.AlgorithmMapper;
import com.qust.endecryptionmanager.pojo.AlgorithmInfo;
import com.qust.endecryptionmanager.pojo.Configuration;
import com.qust.endecryptionmanager.service.AlgorithmService;
import com.qust.endecryptionmanager.utils.MD5;
import com.qust.util.NettyUtils;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import lombok.extern.log4j.Log4j2;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

@Service
@Log4j2
public class AlgorithmServiceImp implements AlgorithmService {
    private File file;
    private AbstractEnDecryptionWithKeyPair abstractEnDecryptionWithKeyPair;
    private final Map<String , AbstractEnDecryptionWithKeyPair> map;
    private final AlgorithmMapper algorithmMapper;
    private final Configuration configuration;
    private final NettyUtils.MessageHandle messageHandle;
    private final EventLoopGroup eventLoopGroup;
    private final CuratorFramework zkClient;
    public AlgorithmServiceImp(Map<String, AbstractEnDecryptionWithKeyPair> map, AlgorithmMapper algorithmMapper, Configuration configuration, EventLoopGroup eventLoopGroup, NettyUtils.MessageHandle messageHandle, CuratorFramework zkClient) {
        this.map = map;
        this.algorithmMapper = algorithmMapper;
        this.configuration = configuration;
        this.eventLoopGroup = eventLoopGroup;
        this.messageHandle = messageHandle;
        this.zkClient = zkClient;
    }


    @Override
    public void uploadInfo(AlgorithmInfo algorithmInfo,byte[] jar) {
        algorithmMapper.uploadInfo(algorithmInfo);
        saveJar(algorithmInfo.getJarPath(),jar);
        createInstance(algorithmInfo.getClassPath(),algorithmInfo.getName(),jar);
    }

    @Override
    public String checkIntegrity() {
        String result;
        try{
            byte[] bytes = zkClient.getData().forPath("/config/meta");
            result = MD5.bytesToHex(bytes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    @Override
    public void resourceScheduling(String address, Set<Integer> set) {
        String local = configuration.getServerIp()+":"+configuration.getServerPort();
        NettyUtils.MessageHandle messageHandle1 = new NettyUtils.MessageHandle();
        eventLoopGroup.next().execute(()->{
            log.debug("{},追赶",address);
            Channel channel = NettyUtils.generateChannel(configuration.getServerIp(), configuration.getServerPort(), eventLoopGroup, messageHandle1);
            if (address.equals(local)){
                List<AlgorithmInfo> algorithmInfos = algorithmMapper.listInfo();
                for (int code : set) {
                    Promise<Message> promise = new DefaultPromise<>(eventLoopGroup.next());
                    messageHandle1.setResultPromise(promise);
                    AtomicReference<String> jarPath = new AtomicReference<>();
                    AtomicReference<String> classPath = new AtomicReference<>();
                    algorithmInfos.stream().filter(algorithmInfo -> algorithmInfo.getId() == code).forEach(algorithmInfo -> {
                        jarPath.set(algorithmInfo.getJarPath());
                        classPath.set(algorithmInfo.getClassPath());
                    });

                    try(
                            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{Paths.get(jarPath.get()).toFile().toURI().toURL()});
                            InputStream inputStream = Files.newInputStream(Paths.get(jarPath.get()));
                    ){
                        byte[] bytes = new byte[inputStream.available()];
                        int read = inputStream.read(bytes);

                        Class<?> aClass = urlClassLoader.loadClass(classPath.get());
                        log.debug("{},实例化",aClass);
                        KeyPair keyPair = KeyPair.generateKeyPair((Class<? extends IEnDecryption>) aClass, configuration.getPassword());
                        log.debug("生成密钥");
                        channel.writeAndFlush(new TransmissionAlgorithmMessage(
                                new Message.AlgorithmTransmissionContext(
                                        code,classPath.get(),keyPair.getPublicKey(),keyPair.getPrivateKey(),bytes,false
                                )
                        ));
                        log.debug("发送算法");
                        promise.get();
                        log.debug("等待重新发送");
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                channel.writeAndFlush(
                        new AlgorithmicResourceCatchUpMessage(true)
                );
                log.debug("{},追赶完毕",address);
            }else {
                /*
                传送address和缺失算法编号给address的上一个节点,让上一个节点给address传送,如果上一个节点也处于追赶阶段,则等待
                 */
                channel.writeAndFlush(new AlgorithmicResourceCatchUpMessage(
                        new Message.AlgorithmicResourceCatchUpContext(
                                address,set
                        )
                ));
            }
            channel.close();
        });

    }

    @Override
    public void deleteJar(String name) {
        algorithmMapper.delete(name);
    }

    private void saveJar(String path,byte[] bytes) {
        file = new File(path);
        try(OutputStream outputStream = Files.newOutputStream(file.toPath())){
            outputStream.write(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void createInstance(String classPath, String algorithmName,byte[] jar) {
        try(URLClassLoader classLoader = new URLClassLoader(new URL[] { file.toURI().toURL() })){
            Class<?> clazz = classLoader.loadClass(classPath);
            abstractEnDecryptionWithKeyPair = (AbstractEnDecryptionWithKeyPair) clazz.newInstance();

            KeyPair keyPair = KeyPair.generateKeyPair((Class<? extends IEnDecryption>) clazz, configuration.getPassword());
            eventLoopGroup.next().execute(()->{
                transmissionInstance(algorithmMapper.selectId(algorithmName),classPath, keyPair.getPublicKey(), keyPair.getPrivateKey(),jar);
            });
            map.put(algorithmName,abstractEnDecryptionWithKeyPair);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private void transmissionInstance(int code,String classPath,String publicKey,String privateKey,byte[] jar) {
        Promise<Message> promise = new DefaultPromise<>(eventLoopGroup.next());
        messageHandle.setResultPromise(promise);
        Channel channel = NettyUtils.generateChannel(configuration.getServerIp(), configuration.getServerPort(), eventLoopGroup, messageHandle);
        channel.writeAndFlush(new TransmissionAlgorithmMessage(
                new Message.AlgorithmTransmissionContext(
                        code,classPath,publicKey,privateKey,jar,false
                )
        ));

        promise.addListener(future -> {
            if (future.isSuccess()){
                log.debug("获取metaData");
                Message message = (Message) future.get();
                JSONObject jsonObject = (JSONObject) message.getContext();
                Result<?> result = jsonObject.to(Result.class);
                String data = (String)result.getData();
                log.debug("数据传输,{}",data);
                zkClient.setData().forPath("/config/meta",data.getBytes(StandardCharsets.UTF_8));
                channel.close();
            }
        });


    }


}
